/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package my.ClientTorrent;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Wico
 */
public class Torrent implements Runnable{
    private FileTorrent fileTorrent;    // metainfo dari torrent
    private File downloadedFile;        // lokasi tempat pendownloadan file
    private long downloaded;            // jumlah byte yang sudah di download
    private long uploaded;              // jumlah byte yang sudah di upload
    private byte state;                 //1 = downloading: 0 = Stop: 2 = finish
    private TrackerResponse tresp;      // pesan terakhir yang diterima torrent
    private String urlSent;             // url terakhir yang dikirim ke traker
//    private ArrayList<Integer> listCompletedPieces; // kumpulan dari piece yang dsudah di download
//    private HashMap<String, Integer> listPeers;
//    private HashMap<String, String> statusPeers;
    public boolean updated;
    private ArrayList<PeerConnection> listConnection;
    private LinkedList<Integer> queueIncomplete;
    
    public Torrent()
    {
        fileTorrent = new FileTorrent();
        downloaded = 0;
        uploaded = 0;
        state = 0;
        tresp = new TrackerResponse();
//        listCompletedPieces = new ArrayList<Integer>();
//        listPeers = new HashMap<String, Integer>();
//        statusPeers = new HashMap<String, String>();
        updated =false;
        queueIncomplete = new LinkedList<Integer>();
    }
        
    public Torrent(FileTorrent fileTorrent, String location)
    {        
        this.fileTorrent = fileTorrent;
        downloaded = 0;
        uploaded = 0;
        state = 0;
        tresp = new TrackerResponse();
        downloadedFile = new File(location+this.fileTorrent.getFilename());
        System.out.println(fileTorrent.getFileSize());
        System.out.println(downloadedFile.getPath());
        if (!downloadedFile.exists()) try {
            downloadedFile.createNewFile();
            FileOutputStream fos = new FileOutputStream(downloadedFile);
            int looping = (int)(fileTorrent.getFileSize()/100000000);
            for (int i = 0 ; i < looping; i++)
                fos.write(new byte[100000000]);
            fos.write(new byte[(int)(fileTorrent.getFileSize()%100000000)]);
            fos.close();
        } catch (IOException ex) {
            Logger.getLogger(Torrent.class.getName()).log(Level.SEVERE, null, ex);
        }
//        listCompletedPieces = new ArrayList<Integer>();
//        listPeers = new HashMap<String, Integer>();
//        statusPeers = new HashMap<String, String>();
        this.cekDownloaded();
        updated =false;
        queueIncomplete = new LinkedList<Integer>();
    }
    
    public long getLeft()
    {
        return getFileTorrent().getFileSize() - getDownloaded();
    }
    
    public String getInfoHash()
    {
        String result = new String();
        MessageDigest mg = null;
        try {
            mg = MessageDigest.getInstance("SHA");
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(Torrent.class.getName()).log(Level.SEVERE, null, ex);
        }
        try {mg.update(getFileTorrent().getFilename().getBytes("ISO_8859-1"));} catch (UnsupportedEncodingException ex) {ex.printStackTrace();}
        byte[] digest = mg.digest();
        for (int i = 0; i < digest.length; i++)
        {
            result += String.format("%02X", digest[i] & 0xff);
        }
        return result;        
        
//        return getFileTorrent().getFilename();
    }

    public String genReqParam() throws NoSuchAlgorithmException
    {
        String result = new String();
        
        result += "infohash=" + getInfoHash() + "&";
        result += "port=" + ClientTorrent.getPort() + "&";
        result += "uploaded=" + getUploaded() + "&";
        result += "downloaded=" + getDownloaded() + "&";
        result += "left=" + getLeft() + "&";
        result += "event=";
        if (getStates()==0)
        {
            result += "stopped";            
        } else if (getStates()==1) {
            result += "started";
        } else if (getStates()==2) {
            result += "completed";
        }
        
        return result;
                
    }
    
    public String sendGetRequest(String reqParam)
    {
        HttpURLConnection conn = null;
        String result = null;
        String urlStr = fileTorrent.getAnnounce();
        
        try
        {
            // construct data
            StringBuilder data = new StringBuilder();
            if (!urlStr.toLowerCase().startsWith("http://")) 
                urlStr = "http://" + urlStr;
            if (urlStr.indexOf(":") != -1)
            {
                int indeksSlash = urlStr.indexOf('/', 7);
                if (indeksSlash != -1) 
                    urlStr = urlStr.substring(0, indeksSlash) + ":" + ClientTorrent.trakerPort + urlStr.substring(indeksSlash);
                else
                    urlStr += ":" + ClientTorrent.trakerPort + "/announce";
            }
            // send data
            if (reqParam != null && reqParam.length() > 0)
            {                
                urlStr += "?" + reqParam;
            }
            urlSent = urlStr;
            URL url = new URL(urlStr);
            System.out.println(url);
            //URLConnection conn = url.openConnection();
            conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");

//            conn.setDoInput(true);
//            conn.setDoOutput(true);
            conn.setReadTimeout(15000);
            
            conn.connect();
            
            // get response
            
            BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            StringBuilder sb = new StringBuilder();
            String line;
            
            while ((line = rd.readLine()) != null)
            {
                line += "\n";
                sb.append(line);
            }
            result = sb.toString();
            rd.close();

        } catch (Exception e)
        {
            e.printStackTrace();
        } 
        finally
        {
            conn.disconnect();
        }
        
        return result;
    }
    
    public void updateResponse() throws NoSuchAlgorithmException
    {
        String result = sendGetRequest(genReqParam());
        StringTokenizer st = new StringTokenizer(result);
        String temp = st.nextToken();
        if (temp.equals("failure_reason")){
            getTresp().setFailure_reason(st.nextToken());       
        }else{                              
            if (temp.equals("warning_message")){
                getTresp().setWarning_message(st.nextToken());        
                st.nextToken();
            }else{
                
            }            
            getTresp().setInterval(Integer.parseInt(st.nextToken()));
            st.nextToken();        
            getTresp().setComplete(Integer.parseInt(st.nextToken()));
            st.nextToken();        
            getTresp().setIncomplete(Integer.parseInt(st.nextToken()));        
            st.nextToken();

            ArrayList<Peer> peertemp = new ArrayList<Peer>();
            while (st.hasMoreTokens())
            {
                String temp2 = st.nextToken();                                
                Peer pir = new Peer(temp2.split(":")[0],Integer.parseInt(temp2.split(":")[1]));
                peertemp.add(pir);
            }
            getTresp().setPeers(peertemp);
            System.out.println(getTresp());
        }
        
    }
    
    public synchronized void updatePeersList()
    {
//        for (String kumpulanIP : getStatusPeers().keySet())
//        {
//            getStatusPeers().put(kumpulanIP,"Disconnected");
//        }
//        for (int i = 0; i < getTresp().getPeers().size(); i++)
//        {
//            getListPeers().put(getTresp().getPeers().get(i).IP, getTresp().getPeers().get(i).port);
//            getStatusPeers().put(getTresp().getPeers().get(i).IP,"Connected");
//        }
//        
//        for (String kumpulanIP : getStatusPeers().keySet())
//        {
//            if (getListPeers().size() <= 50) break;
//            if (getStatusPeers().get(kumpulanIP).equals("Disconnected"))
//            {
//                getStatusPeers().remove(kumpulanIP);
//                getListPeers().remove(kumpulanIP);
//            }
//        }
        for (int i = 0; i < getTresp().getPeers().size(); i++)
        {
            listConnection.add(new PeerConnection(getTresp().getPeers().get(i).IP, getTresp().getPeers().get(i).port));
        }
    }
//    
//    public HashMap<String,Integer> getConnectedPeers()
//    {
//        HashMap<String,Integer> result = new HashMap<String, Integer>();
//        for (String kumpulanIP : getListPeers().keySet())
//        {
//            if (getStatusPeers().get(kumpulanIP).equals("Connected"))
//                result.put(kumpulanIP, getListPeers().get(kumpulanIP));
//        }
//        return result;
//    }
//    
//    public HashMap<String,Integer> getDisconnectedPeers()
//    {
//        HashMap<String,Integer> result = new HashMap<String, Integer>();
//        for (String kumpulanIP : getListPeers().keySet())
//        {
//            if (getStatusPeers().get(kumpulanIP).equals("Disconnected"))
//                result.put(kumpulanIP, getListPeers().get(kumpulanIP));
//        }
//        return result;
//    }
     
    public boolean isStarted()
    {
        return (state == 1);
    }
    
    public boolean isStopped()
    {
        return (state == 0);
    }
    
    public boolean isCompleted()
    {
        return (state == 2);
    }
    
    /**
     * @return the fileTorrent
     */
    public FileTorrent getFileTorrent() {
        return fileTorrent;
    }

    /**
     * @return the downloaded
     */
    public long getDownloaded() {
        return downloaded;
    }

    /**
     * @return the uploaded
     */
    public long getUploaded() {
        return uploaded;
    }

    /**
     * @param fileTorrent the fileTorrent to set
     */
    public void setFileTorrent(FileTorrent fileTorrent) {
        this.fileTorrent = fileTorrent;
    }

    /**
     * @param downloaded the downloaded to set
     */
    public void setDownloaded(long downloaded) {
        this.downloaded = downloaded;
    }

    /**
     * @param uploaded the uploaded to set
     */
    public void setUploaded(long uploaded) {
        this.uploaded = uploaded;
    }

    public void cekDownloaded()
    {
        FileInputStream fis = null;
        downloaded = 0;
        try {
            fis = new FileInputStream(downloadedFile);
            for (int i = 0 ; i < fileTorrent.getPiecesAmount(); i++)
            {
                byte[] buf = new byte[fileTorrent.getPieceLength()];
                int len = fis.read(buf);
                MessageDigest md = MessageDigest.getInstance("SHA");
                md.update(buf);
                byte[] digest = md.digest();
                String hashed = new String();
                for (int j = 0; j < digest.length; j++)
                {
                    hashed += String.format("%02X", digest[j] & 0xFF);
                }
                if (hashed.equals(fileTorrent.getPieces().get(i)))
                {
                    downloaded += len;
//                    listCompletedPieces.add(i);
                }
                else
                    queueIncomplete.push(i);
            }            
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(Torrent.class.getName()).log(Level.SEVERE, null, ex);
        } catch (FileNotFoundException ex) {
            Logger.getLogger(Torrent.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(Torrent.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                fis.close();
            } catch (IOException ex) {
                Logger.getLogger(Torrent.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        if (getLeft() == 0) state = 2;
        else
            state = 1;
    }
    
    /**
     * @return the state
     */
    public byte getStates() {
        return state;
    }

    /**
     * @param state the state to set
     */
    public void setState(byte state) {
        this.state = state;
    }

    @Override
    public void run() {
        while (!isStopped())
        {
            try {
                updateResponse();
                updatePeersList();
                updated = true;
            } catch (NoSuchAlgorithmException ex) {
                Logger.getLogger(Torrent.class.getName()).log(Level.SEVERE, null, ex);
            }
            
            try {
//                wait(tresp.getInterval()*1000);                
                Thread.sleep(getTresp().getInterval()*1000);
            } catch (InterruptedException ex) {
                System.out.println("Interrupted!!!");
            }
        }
        try {
            updateResponse();
            updated = true;
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(Torrent.class.getName()).log(Level.SEVERE, null, ex);
        }
        
    }

    /**
     * @return the downloadedFile
     */
    public File getDownloadedFile() {
        return downloadedFile;
    }

    /**
     * @param downloadedFile the downloadedFile to set
     */
    public void setDownloadedFile(File downloadedFile) {
        this.downloadedFile = downloadedFile;
    }

    /**
     * @return the tresp
     */
    public TrackerResponse getTresp() {
        return tresp;
    }

    /**
     * @param tresp the tresp to set
     */
    public void setTresp(TrackerResponse tresp) {
        this.tresp = tresp;
    }

    /**
     * @return the urlSent
     */
    public String getUrlSent() {
        return urlSent;
    }

//    /**
//     * @return the listPeers
//     */
//    public HashMap<String, Integer> getListPeers() {
//        return listPeers;
//    }
//
//    /**
//     * @return the statusPeers
//     */
//    public HashMap<String, String> getStatusPeers() {
//        return statusPeers;
//    }
}
